"""
CSV数据表格视图
支持加载CSV文件，Notes列使用复选框下拉框
"""

import sys
import csv
import time  # 添加时间统计
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QTableView, QVBoxLayout, QWidget,
    QPushButton, QFileDialog, QHeaderView, QStyledItemDelegate,
    QComboBox, QCheckBox, QHBoxLayout, QLabel, QLineEdit, QMessageBox,
    QDockWidget, QTextEdit, QDateEdit,QStyleOptionComboBox, QStyle
)
from PyQt5.QtCore import Qt, QAbstractTableModel, QModelIndex, QVariant, QTimer, QDate, QEvent
from PyQt5.QtGui import QColor, QDoubleValidator


class RDateDelegate(QStyledItemDelegate):
    """日期列的代理"""
    _editor_count = 0  # 统计编辑器创建次数
    def createEditor(self, parent, option, index):
        """创建日期编辑器"""
        editor = QDateEdit(parent)
        RDateDelegate._editor_count += 1
        editor_id = RDateDelegate._editor_count
        print(f"[Delegate] 创建编辑器 #{editor_id} - parent: {parent.__class__.__name__}")
        editor.setCalendarPopup(True) 
        editor.setDisplayFormat('yyyy-MM-dd')  
        editor.destroyed.connect(lambda: print(f"[Delegate] 编辑器 #{editor_id} 已销毁"))
        return editor
    
    def setEditorData(self, editor, index):
        """设置编辑器的当前值"""
        value = index.model().data(index, Qt.EditRole)
        if value:
            try:
                date = QDate.fromString(str(value), 'yyyy-MM-dd')
                if date.isValid():
                    editor.setDate(date)
                else:
                    editor.setDate(QDate.currentDate())
            except:
                editor.setDate(QDate.currentDate())
        else:
            editor.setDate(QDate.currentDate())
    
    def setModelData(self, editor, model, index):
        """将编辑器的值保存到模型"""
        date = editor.date()
        value = date.toString('yyyy-MM-dd')
        model.setData(index, value, Qt.EditRole)


class RNumericRangeDelegate(QStyledItemDelegate):
    """数字范围LineEdit代理（用于price和discount）"""
    _editor_count = 0  # 统计编辑器创建次数
    def __init__(self, min_value=0.0, max_value=1.0, decimals=2, parent=None):
        super().__init__(parent)
        self.min_value = min_value
        self.max_value = max_value
        self.decimals = decimals
    
    def createEditor(self, parent, option, index):
        """创建数字输入编辑器"""
        editor = QLineEdit(parent)
        RNumericRangeDelegate._editor_count += 1
        editor_id = RNumericRangeDelegate._editor_count
        print(f"[Delegate] 创建编辑器 #{editor_id} - parent: {parent.__class__.__name__}")
        # 设置数字验证器
        validator = QDoubleValidator(self.min_value, self.max_value, self.decimals, editor)
        validator.setNotation(QDoubleValidator.StandardNotation)
        editor.setValidator(validator)
        editor.destroyed.connect(lambda: print(f"[Delegate] 编辑器 #{editor_id} 已销毁"))
        return editor
    
    def setEditorData(self, editor, index):
        """设置编辑器的当前值"""
        value = index.model().data(index, Qt.EditRole)
        if value:
            editor.setText(str(value))
        else:
            editor.setText('0.0')
    
    def setModelData(self, editor, model, index):
        """将编辑器的值保存到模型"""
        text = editor.text()
        try:
            value = float(text) if text else 0.0
            # 确保值在范围内
            value = max(self.min_value, min(self.max_value, value))
            model.setData(index, value, Qt.EditRole)
        except ValueError:
            # 如果转换失败，保持原值
            pass


class RComboBoxDelegate(QStyledItemDelegate):
    """Notes列的复选框下拉代理"""
    
    #NOTES_OPTIONS = ['notes', 'Return', 'Gift', 'VIP']
    NOTES_OPTIONS = []
    for i in range(100):
        NOTES_OPTIONS.append(str(i))
    _editor_count = 0  # 统计编辑器创建次数
    
    def editorEvent(self, event, model, option, index):
        """
        捕获单击事件，单击即触发编辑
        """
        if event.type() == QEvent.MouseButtonRelease:
            # 单击时打开编辑器
            if event.button() == Qt.LeftButton:
                # 通知View进入编辑模式
                view = option.widget
                if view:
                    view.edit(index)
                return True
        
        return super().editorEvent(event, model, option, index)
    
    def createEditor(self, parent, option, index):
        """创建编辑器 - 复选框下拉组合框"""
        RComboBoxDelegate._editor_count += 1
        editor_id = RComboBoxDelegate._editor_count
        
        print(f"[Delegate] 创建编辑器 #{editor_id} - parent: {parent.__class__.__name__}")
        
        editor = QComboBox(parent)
        editor.setEditable(False)
        
        # 添加所有选项
        for opt in self.NOTES_OPTIONS:
            editor.addItem(opt)
        
        # 自定义showPopup：滚动到顶部 + 显示在正下方
        original_show_popup = editor.showPopup
        def custom_show_popup():
            # 先执行原始的showPopup
            original_show_popup()
            
            # 滚动到顶部
            list_view = editor.view()
            if list_view:
                list_view.scrollToTop()
                
                # 获取下拉框容器（popup）
                popup = list_view.parent()
                if popup:
                    # 计算ComboBox在屏幕上的全局位置
                    combo_pos = editor.mapToGlobal(editor.rect().bottomLeft())
                    
                    # 设置popup显示在ComboBox正下方
                    popup.move(combo_pos)
        
        editor.showPopup = custom_show_popup
        
        # 监听销毁事件
        editor.destroyed.connect(lambda: print(f"[Delegate] 编辑器 #{editor_id} 已销毁"))
        
        return editor
    
    def setEditorData(self, editor, index):
        """设置编辑器数据"""
        value = index.model().data(index, Qt.EditRole)
        if value:
            # 查找匹配的选项（不区分大小写）
            value_lower = value.lower()
            for i, opt in enumerate(self.NOTES_OPTIONS):
                if opt.lower() == value_lower:
                    editor.setCurrentIndex(i)
                    return
        editor.setCurrentIndex(0)
    def paint(self, painter, option, index):
        
        #1. 组装 QStyleOptionComboBox
        # combo_opt = QStyleOptionComboBox()
        # combo_opt.rect = option.rect                      # 单元格矩形
        # combo_opt.state = option.state | QStyle.State_Enabled
        # combo_opt.currentText = index.data(Qt.DisplayRole) or ''
        # combo_opt.editable = False                        # 只读外观

        # # 2. 用风格引擎画整个 CC_ComboBox
        # QApplication.style().drawComplexControl(QStyle.CC_ComboBox,
        #                                       combo_opt,
        #                                       painter)
        super().paint(painter,option,index)
    
    def setModelData(self, editor, model, index):
        """将编辑器数据写回模型"""
        value = editor.currentText()
        model.setData(index, value, Qt.EditRole)
    
    def updateEditorGeometry(self, editor, option, index):
        """更新编辑器几何位置"""
        editor.setGeometry(option.rect)


class RCSVTableModel(QAbstractTableModel):
    """CSV数据模型 - 支持大数据量优化"""
    
    def __init__(self, data=None, headers=None, parent=None):
        super().__init__(parent)
        self._data = data or []
        self._headers = headers or []
        self._filtered_indices = None 
        self._use_filter = False
        self._render_count = 0 
        self._rendered_rows = set() 
        self._last_call_log = [] 
        self._cache_hit_count = 0 
    
    def rowCount(self, parent=QModelIndex()):
        if self._use_filter and self._filtered_indices is not None:
            return len(self._filtered_indices)
        return len(self._data)
    
    def columnCount(self, parent=QModelIndex()):
        return len(self._headers) if self._headers else 0
    
    def data(self, index, role=Qt.DisplayRole):
        if not index.isValid():
            return QVariant()
        
        row = index.row()
        col = index.column()
        
        # 获取实际数据行索引
        actual_row = self._get_actual_row(display_row=row)
        if actual_row >= len(self._data):
            return QVariant()
        
        # 记录渲染统计（验证虚拟化）
        if col == 0:  # 只统计第一列，避免重复计数
            self._rendered_rows.add(actual_row)
            self._render_count += 1
            
            # 记录最近的调用（用于观察缓存行为）
            call_info = f"Row {actual_row}"
            if len(self._last_call_log) >= 10:
                self._last_call_log.pop(0)
            self._last_call_log.append(call_info)
        
        if role == Qt.DisplayRole or role == Qt.EditRole:
            return self._data[actual_row][col]
        
        # 为Notes列添加背景色
        if role == Qt.BackgroundRole:
            actual_row = self._get_actual_row(row)
            return QColor(255, 230, 230)  # 浅红色
            if actual_row < len(self._data):
                value = self._data[actual_row][col]
                if value:
                    value_lower = value.lower()
                    if value_lower == 'return':
                        return QColor(255, 230, 230)  # 浅红色
                    elif value_lower == 'gift':
                        return QColor(230, 255, 230)  # 浅绿色
                    elif value_lower == 'vip':
                        return QColor(255, 245, 200)  # 浅黄色
        
        return QVariant()
    
    def _get_actual_row(self, display_row):
        """获取实际数据行索引"""
        if self._use_filter and self._filtered_indices is not None:
            if display_row < len(self._filtered_indices):
                return self._filtered_indices[display_row]
            return len(self._data)  # 超出范围
        return display_row
    
    def setData(self, index, value, role=Qt.EditRole):
        """设置数据"""
        if role == Qt.EditRole:
            row = index.row()
            col = index.column()
            actual_row = self._get_actual_row(row)
            if actual_row < len(self._data):
                self._data[actual_row][col] = value
                self.dataChanged.emit(index, index, [Qt.DisplayRole, Qt.EditRole])
                return True
        return False
    
    def headerData(self, section, orientation, role=Qt.DisplayRole):
        if role == Qt.DisplayRole:
            if orientation == Qt.Horizontal:
                return self._headers[section] if section < len(self._headers) else ''
            else:
                return str(section + 1)
        return QVariant()
    
    def flags(self, index):
        """设置单元格标志"""
        if not index.isValid():
            return Qt.ItemIsEnabled
        
        col = index.column()
        col_name = self._headers[col] if col < len(self._headers) else ''
        
        # 设置可编辑的列：order_date, price, discount, notes
        editable_columns = ['order_date', 'price', 'discount', 'notes']
        
        if col_name in editable_columns:
            return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
        
        return Qt.ItemIsEnabled | Qt.ItemIsSelectable
    
    def load_csv(self, filepath, max_rows=None):
        """加载CSV文件（支持限制行数）"""
        start_time = time.time()  # 开始计时
        
        try:
            self.beginResetModel()
            with open(filepath, 'r', encoding='utf-8') as f:
                reader = csv.reader(f)
                self._headers = next(reader)  # 读取表头
                
                # 读取数据（可选限制行数）
                if max_rows:
                    self._data = []
                    for i, row in enumerate(reader):
                        if i >= max_rows:
                            break
                        self._data.append(row)
                else:
                    self._data = [row for row in reader]
            
            self._use_filter = False
            self._filtered_indices = None
            self.endResetModel()
            
            # 统计加载时间
            elapsed_time = time.time() - start_time
            load_type = f"分批加载({max_rows}行)" if max_rows else "全量加载"
            message = f"成功加载 {len(self._data)} 行数据 | {load_type} | 耗时: {elapsed_time:.3f}秒"
            
            print(f"[Performance] {message}")
            return True, message
        except Exception as e:
            elapsed_time = time.time() - start_time
            self.endResetModel()
            return False, f"加载失败: {str(e)} | 耗时: {elapsed_time:.3f}秒"
    
    def get_data(self):
        """获取所有数据"""
        return self._data
    
    def get_headers(self):
        """获取表头"""
        return self._headers
    
    def filter_data(self, search_text):
        """过滤数据"""
        if not search_text:
            self.beginResetModel()
            self._use_filter = False
            self._filtered_indices = None
            self.endResetModel()
            return
        
        self.beginResetModel()
        search_lower = search_text.lower()
        self._filtered_indices = []
        
        for i, row in enumerate(self._data):
            # 搜索所有列
            if any(search_lower in str(cell).lower() for cell in row):
                self._filtered_indices.append(i)
        
        self._use_filter = True
        self.endResetModel()
    
    def get_filtered_row_count(self):
        """获取过滤后的行数"""
        if self._use_filter and self._filtered_indices is not None:
            return len(self._filtered_indices)
        return len(self._data)
    
    def get_render_stats(self):
        """获取渲染统计信息"""
        return {
            'total_data_calls': self._render_count,
            'unique_rows_rendered': len(self._rendered_rows),
            'total_rows': len(self._data),
            'last_calls': self._last_call_log.copy(),
            'cache_hits': self._cache_hit_count
        }
    
    def reset_render_stats(self):
        """重置渲染统计"""
        self._render_count = 0
        self._rendered_rows.clear()
        self._last_call_log.clear()
        self._cache_hit_count = 0
    
    def append_rows(self, new_rows):
        """追加新行数据（用于懒加载）"""
        if not new_rows:
            return
        
        # 获取当前行数
        start_row = len(self._data)
        
        # 开始插入操作
        self.beginInsertRows(QModelIndex(), start_row, start_row + len(new_rows) - 1)
        
        # 追加数据
        self._data.extend(new_rows)
        
        # 结束插入
        self.endInsertRows()
        
        return len(new_rows)


class RCSVTableView(QMainWindow):
    """CSV表格视图主窗口"""
    
    def __init__(self):
        super().__init__()
        self.model = RCSVTableModel()
        self.stats_dock = None  # 统计面板
        self.stats_timer = None  # 定时器
        self.csv_file_path = None  # 保存CSV文件路径
        self.total_rows_in_file = 0  # 文件总行数
        self.is_loading = False  # 是否正在加载
        
        # 创建代理
        self.checkbox_delegate = RComboBoxDelegate()  # Notes列
        self.date_delegate = RDateDelegate()  # order_date列
        self.price_delegate = RNumericRangeDelegate(min_value=0.0, max_value=99999.0, decimals=2)  # price列
        self.discount_delegate = RNumericRangeDelegate(min_value=0.0, max_value=1.0, decimals=2)  # discount列
        
        self.init_ui()
        
        # 自动加载big_demo.csv
        import os
        csv_path = os.path.join(os.path.dirname(__file__), 'big_demo.csv')
        if os.path.exists(csv_path):
            self.load_csv_file(csv_path, max_rows=None)  # 初始加载100行
    
    def init_ui(self):
        """初始化UI"""
        self.setWindowTitle('CSV表格查看器 - Notes复选框下拉')
        self.resize(1200, 700)
        
        # 主窗口部件
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        layout = QVBoxLayout(central_widget)
        
        # 工具栏
        toolbar_layout = QHBoxLayout()
        
        self.load_btn = QPushButton('加载CSV文件')
        self.load_btn.clicked.connect(self.load_csv)
        toolbar_layout.addWidget(self.load_btn)
        
        self.load_all_btn = QPushButton('加载全部数据')
        self.load_all_btn.clicked.connect(self.load_all_csv)
        toolbar_layout.addWidget(self.load_all_btn)
        
        # 性能测试按钮
        self.perf_test_btn = QPushButton('性能对比测试')
        self.perf_test_btn.clicked.connect(self.performance_test)
        toolbar_layout.addWidget(self.perf_test_btn)
        
        self.save_btn = QPushButton('保存CSV文件')
        self.save_btn.clicked.connect(self.save_csv)
        toolbar_layout.addWidget(self.save_btn)
        
        toolbar_layout.addWidget(QLabel('搜索:'))
        self.search_input = QLineEdit()
        self.search_input.setPlaceholderText('输入关键词搜索...')
        self.search_input.textChanged.connect(self.search_data)
        self.search_input.returnPressed.connect(self.perform_search)
        toolbar_layout.addWidget(self.search_input)
        
        self.search_btn = QPushButton('搜索')
        self.search_btn.clicked.connect(self.perform_search)
        toolbar_layout.addWidget(self.search_btn)
        
        self.clear_filter_btn = QPushButton('清除过滤')
        self.clear_filter_btn.clicked.connect(self.clear_filter)
        toolbar_layout.addWidget(self.clear_filter_btn)
        
        self.row_count_label = QLabel('行数: 0')
        toolbar_layout.addWidget(self.row_count_label)
        
        toolbar_layout.addStretch()
        
        layout.addLayout(toolbar_layout)
        
        # 为表格视图设置代理
        self.table_view = QTableView()
        self.table_view.setModel(self.model)
        self.table_view.setAlternatingRowColors(True)
        self.table_view.setSortingEnabled(False)  # 大数据禁用排序提升性能
        
        # 性能优化设置
        self.table_view.setVerticalScrollMode(QTableView.ScrollPerPixel)
        self.table_view.setHorizontalScrollMode(QTableView.ScrollPerPixel)
        self.table_view.verticalHeader().setDefaultSectionSize(24)  # 固定行高
        self.table_view.verticalHeader().setSectionResizeMode(QHeaderView.Fixed)
        
        # 监听滚动事件（可选）
        scrollbar = self.table_view.verticalScrollBar()
        scrollbar.valueChanged.connect(self.on_scroll)
        
        # 设置表头自适应
        header = self.table_view.horizontalHeader()
        header.setSectionResizeMode(QHeaderView.Interactive)
        header.setStretchLastSection(True)
        
        layout.addWidget(self.table_view)
        
        # 创建统计DockWidget
        self.create_stats_dock()
        
        # 状态栏
        self.statusBar().showMessage('就绪')
        
        # 启动定时器更新统计信息
        self.start_stats_timer()
    
    def load_csv(self):
        """打开文件对话框加载CSV"""
        filepath, _ = QFileDialog.getOpenFileName(
            self, '选择CSV文件', '', 'CSV文件 (*.csv);;所有文件 (*.*)'
        )
        if filepath:
            self.load_csv_file(filepath)
    
    def load_csv_file(self, filepath, max_rows=None):
        """加载CSV文件"""
        # 保存文件路径供后续加载使用
        self.csv_file_path = filepath
        
        # 先读取文件总行数
        try:
            with open(filepath, encoding='utf-8') as f:
                next(f, None)          # 跳过标题行；文件空也安全
                self.total_rows_in_file = sum(1 for _ in f)
        except Exception as e:
            self.total_rows_in_file = 0
            print(f"无法统计文件行数: {e}")
        
        success, message = self.model.load_csv(filepath, max_rows)
        
        if success:
            # 获取列索引（根据CSV表头）
            headers = self.model.get_headers()
            
            # user_id,order_date,category,product_id,qty,price,discount,revenue,notes
            if 'order_date' in headers:
                date_col = headers.index('order_date')
                self.table_view.setItemDelegateForColumn(date_col, self.date_delegate)
            
            if 'price' in headers:
                price_col = headers.index('price')
                self.table_view.setItemDelegateForColumn(price_col, self.price_delegate)
            
            if 'discount' in headers:
                discount_col = headers.index('discount')
                self.table_view.setItemDelegateForColumn(discount_col, self.discount_delegate)
            
            if 'notes' in headers:
                notes_col = headers.index('notes')
                self.table_view.setItemDelegateForColumn(notes_col, self.checkbox_delegate)
            
            # 更新行数显示
            loaded_rows = self.model.rowCount()
            if loaded_rows < self.total_rows_in_file:
                self.row_count_label.setText(f'行数: {loaded_rows} / {self.total_rows_in_file} (滚动自动加载)')
            else:
                self.row_count_label.setText(f'行数: {loaded_rows}')
            
            self.statusBar().showMessage(message)
            
            # 自适应列宽
            self.table_view.resizeColumnsToContents()
        else:
            self.statusBar().showMessage(message)
            QMessageBox.warning(self, '加载错误', message)
    
    def save_csv(self):
        """保存CSV文件"""
        filepath, _ = QFileDialog.getSaveFileName(
            self, '保存CSV文件', '', 'CSV文件 (*.csv);;所有文件 (*.*)'
        )
        if filepath:
            try:
                with open(filepath, 'w', encoding='utf-8', newline='') as f:
                    writer = csv.writer(f)
                    writer.writerow(self.model.get_headers())
                    writer.writerows(self.model.get_data())
                
                self.statusBar().showMessage(f'成功保存到: {filepath}')
                QMessageBox.information(self, '保存成功', f'数据已保存到:\n{filepath}')
            except Exception as e:
                error_msg = f'保存失败: {str(e)}'
                self.statusBar().showMessage(error_msg)
                QMessageBox.critical(self, '保存错误', error_msg)
    
    def search_data(self, text):
        """搜索输入变化时的提示"""
        if text:
            self.statusBar().showMessage(f'输入搜索关键词: {text} (按Enter或点击搜索按钮)')
        else:
            self.statusBar().showMessage('就绪')
    
    def perform_search(self):
        """执行搜索过滤"""
        search_text = self.search_input.text().strip()
        if search_text:
            self.statusBar().showMessage(f'正在搜索: {search_text}...')
            QApplication.processEvents()  # 更新UI
            
            self.model.filter_data(search_text)
            filtered_count = self.model.get_filtered_row_count()
            self.row_count_label.setText(f'显示: {filtered_count} / {len(self.model.get_data())}')
            self.statusBar().showMessage(f'找到 {filtered_count} 条匹配记录')
        else:
            self.clear_filter()
    
    def clear_filter(self):
        """清除过滤"""
        self.search_input.clear()
        self.model.filter_data('')
        self.row_count_label.setText(f'行数: {self.model.rowCount()}')
        self.statusBar().showMessage('已清除过滤')
    
    def load_all_csv(self):
        """加载全部CSV数据"""
        import os
        csv_path = os.path.join(os.path.dirname(__file__), 'big_demo.csv')
        if os.path.exists(csv_path):
            reply = QMessageBox.question(
                self, '确认加载',
                '将加载全部10万行数据，可能需要一些时间。\n是否继续？',
                QMessageBox.Yes | QMessageBox.No
            )
            if reply == QMessageBox.Yes:
                self.statusBar().showMessage('正在加载全部数据...')
                QApplication.processEvents()
                self.load_csv_file(csv_path, max_rows=None)
        else:
            QMessageBox.warning(self, '文件不存在', '未找到big_demo.csv文件')
    
    def on_scroll(self, value):
        """滚动条值改变时触发 - 实现自动懒加载"""
        # 搜索过滤状态下不自动加载
        if self.model._use_filter:
            return
        
        # 获取滚动条信息
        scrollbar = self.table_view.verticalScrollBar()
        total = scrollbar.maximum()
        
        # 滚动到底部80%时自动加载更多数据
        if total > 0 and value >= total * 0.8:
            current_rows = self.model.rowCount()
            
            # 检查是否还有未加载的数据
            if (not self.is_loading and 
                self.csv_file_path and 
                current_rows < self.total_rows_in_file):
                
                self.auto_load_more_rows()
    
    def auto_load_more_rows(self):
        """自动加载更多行（每次加载1000行）"""
        if self.is_loading:
            return
        
        self.is_loading = True
        current_rows = self.model.rowCount()
        remaining = self.total_rows_in_file - current_rows
        
        # 每次加载1000行
        batch_size = min(1000, remaining)
        
        batch_start_time = time.time()  # 单批次计时
        
        self.statusBar().showMessage(f'正在加载更多数据... ({current_rows} -> {current_rows + batch_size})')
        QApplication.processEvents()  # 更新UI
        
        try:
            # 读取额外的数据
            import csv
            with open(self.csv_file_path, 'r', encoding='utf-8') as f:
                reader = csv.reader(f)
                headers = next(reader)  # 跳过表头
                
                # 跳过已加载的行
                for _ in range(current_rows):
                    next(reader)
                
                # 读取新的batch_size行
                new_rows = []
                for i, row in enumerate(reader):
                    if i >= batch_size:
                        break
                    new_rows.append(row)
                
                # 追加到模型
                if new_rows:
                    self.model.append_rows(new_rows)
                    
                    batch_elapsed = time.time() - batch_start_time
                    
                    # 更新显示
                    loaded_rows = self.model.rowCount()
                    if loaded_rows < self.total_rows_in_file:
                        self.row_count_label.setText(f'行数: {loaded_rows} / {self.total_rows_in_file} (滚动自动加载)')
                        status_msg = f'已加载 {loaded_rows} / {self.total_rows_in_file} 行 | 此批耗时: {batch_elapsed:.3f}秒'
                        self.statusBar().showMessage(status_msg)
                        print(f"[Performance] 懒加载批次 | 加载 {batch_size} 行 | 耗时: {batch_elapsed:.3f}秒")
                    else:
                        self.row_count_label.setText(f'行数: {loaded_rows} (全部加载完成)')
                        self.statusBar().showMessage('所有数据已加载完成！')
                        print(f"[Performance] 懒加载完成 | 总行数: {loaded_rows}")
        
        except Exception as e:
            error_msg = f'加载更多数据失败: {str(e)}'
            self.statusBar().showMessage(error_msg)
            print(error_msg)
        
        finally:
            self.is_loading = False
    
    def create_stats_dock(self):
        """创建渲染统计DockWidget"""
        self.stats_dock = QDockWidget('渲染统计 (虚拟化验证)', self)
        self.stats_dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
        
        # 创建统计显示控件
        stats_widget: QWidget = QWidget()
        stats_layout = QVBoxLayout(stats_widget)
        
        # 标题
        title_label = QLabel('实时渲染统计')
        title_label.setStyleSheet('font-weight: bold; font-size: 14px; padding: 5px;')
        stats_layout.addWidget(title_label)
        
        # 统计信息文本
        self.stats_text: QTextEdit = QTextEdit()
        self.stats_text.setReadOnly(True)
        # 移除固定高度，让其自适应填充剩余空间
        self.stats_text.setStyleSheet("""
            QTextEdit {
                background-color: #2b2b2b;
                color: #00ff00;
                font-family: 'Consolas', 'Monaco', monospace;
                font-size: 11px;
                border: 1px solid #555;
                padding: 5px;
            }
        """)
        stats_layout.addWidget(self.stats_text, 1)  # stretch=1 让其占据剩余空间
        
        # 说明文本
        info_label: QLabel = QLabel(
            '说明:\n'
            '• 总数据行数: CSV文件总行数\n'
            '• 已渲染行数: 实际调用data()的行\n'
            '• 调用次数: data()方法总调用数\n'
            '• 虚拟化率: 未渲染行数的百分比\n'
            '• 最近调用: 显示最近请求的行号\n\n'
            '实验观察:\n'
            '1. 保持静止: 调用次数不增加\n'
            '2. 滚动一点: 只新增可见行\n'
            '3. 反复滚动: 可能复用缓存'
        )
        info_label.setWordWrap(True)
        info_label.setStyleSheet('font-size: 12px; color: #666; padding: 5px;')
        stats_layout.addWidget(info_label, 0)  # stretch=0 保持原大小
        
        # 控制按钮
        btn_layout = QHBoxLayout()
        
        reset_btn = QPushButton('重置统计')
        reset_btn.clicked.connect(self.reset_stats)
        btn_layout.addWidget(reset_btn)
        
        refresh_btn = QPushButton('立即刷新')
        refresh_btn.clicked.connect(self.update_stats_display)
        btn_layout.addWidget(refresh_btn)
        
        stats_layout.addLayout(btn_layout, 0)  # stretch=0 保持按钮大小不变
        # 移除addStretch，让stats_text自动填充剩余空间
        
        self.stats_dock.setWidget(stats_widget)
        
        # 添加到主窗口左侧
        self.addDockWidget(Qt.LeftDockWidgetArea, self.stats_dock)
    
    def start_stats_timer(self):
        """启动定时器，每秒更新统计信息"""
        self.stats_timer = QTimer(self)
        self.stats_timer.timeout.connect(self.update_stats_display)
        self.stats_timer.start(1000)  # 每1秒更新一次
    
    def update_stats_display(self):
        """更新统计信息显示"""
        stats = self.model.get_render_stats()
        total = stats['total_rows']
        rendered = stats['unique_rows_rendered']
        calls = stats['total_data_calls']
        last_calls = stats['last_calls']
        
        # 计算虚拟化率
        virtualization_rate = 0
        if total > 0:
            virtualization_rate = ((total - rendered) / total) * 100
        
        # 格式化最近调用记录
        recent_calls_str = '\n'.join(last_calls[-10:]) if last_calls else '(暂无调用)'
        
        # 格式化显示
        stats_text = f"""
╔════════════════════════════╗
║   实时渲染统计信息         ║
╚════════════════════════════╝

总数据行数:     {total:,}
已渲染行数:     {rendered:,}
data()调用次数: {calls:,}
虚拟化率:       {virtualization_rate:.1f}%

状态: {' ✓ 虚拟化正常工作' if virtualization_rate > 50 else ' ⚠ 可能加载了过多数据'}

{'─' * 30}
最近调用的行号 (观察缓存):
{recent_calls_str}
{'─' * 30}

💡 实验提示:
• 保持不动: 调用次数应该停止增长
• 轻微滚动: 只增加新可见行的调用
• 往回滚: 可能重新调用(取决于Qt缓存)
"""
        
        self.stats_text.setPlainText(stats_text)
    
    def reset_stats(self):
        """重置渲染统计"""
        self.model.reset_render_stats()
        self.update_stats_display()
        self.statusBar().showMessage('统计已重置', 2000)
    
    def performance_test(self):
        """性能对比测试：分批加载 vs 全量加载"""
        import os
        csv_path = os.path.join(os.path.dirname(__file__), 'big_demo.csv')
        
        if not os.path.exists(csv_path):
            QMessageBox.warning(self, '文件不存在', '未找到big_demo.csv文件')
            return
        
        # 确认对话框
        reply = QMessageBox.question(
            self, '性能测试',
            '将进行以下测试：\n'
            '1. 分批加载（10,000行）\n'
            '2. 全量加载（所有数据）\n\n'
            '是否继续？',
            QMessageBox.Yes | QMessageBox.No
        )
        
        if reply != QMessageBox.Yes:
            return
        
        results = []
        
        # 测试1：分批加载 10,000行
        print("\n" + "="*60)
        print("测试1: 分批加载 10,000行")
        print("="*60)
        
        test1_start = time.time()
        success1, msg1 = self.model.load_csv(csv_path, max_rows=10000)
        test1_time = time.time() - test1_start
        results.append(f"分批加载(10,000行): {test1_time:.3f}秒")
        
        # 等待界面更新
        QApplication.processEvents()
        time.sleep(0.5)
        
        # 测试2：全量加载
        print("\n" + "="*60)
        print("测试2: 全量加载（所有数据）")
        print("="*60)
        
        test2_start = time.time()
        success2, msg2 = self.model.load_csv(csv_path, max_rows=None)
        test2_time = time.time() - test2_start
        results.append(f"全量加载({self.model.rowCount()}行): {test2_time:.3f}秒")
        
        # 计算性能提升
        if test2_time > 0:
            speedup = test2_time / test1_time
            improvement = ((test2_time - test1_time) / test2_time) * 100
            results.append(f"\n性能提升: {speedup:.2f}x")
            results.append(f"时间节省: {improvement:.1f}%")
        
        # 显示结果
        result_text = "\n".join(results)
        print("\n" + "="*60)
        print("性能测试结果:")
        print("="*60)
        print(result_text)
        print("="*60 + "\n")
        
        QMessageBox.information(
            self, 
            '性能测试结果',
            f'测试完成！\n\n{result_text}'
        )
    
    def show_render_stats(self):
        """显示渲染统计信息 - 验证虚拟化机制"""
        stats = self.model.get_render_stats()
        
        msg = f"""渲染统计信息（验证虚拟化机制）:

总数据行数: {stats['total_rows']}
实际渲染的不同行数: {stats['unique_rows_rendered']}
data()方法调用总次数: {stats['total_data_calls']}

说明:
- 如果"实际渲染的行数"远小于"总数据行数"
  说明Qt确实只渲染了可见区域！
- 滚动时会看到渲染的行数逐渐增加
- 但总是只有少量行被同时渲染
"""
        
        QMessageBox.information(self, '渲染统计', msg)
        
        # 重置统计
        self.model.reset_render_stats()


def main():
    """主函数"""
    app = QApplication(sys.argv)
    
    # 设置应用样式
    app.setStyle('Fusion')
    
    window = RCSVTableView()
    window.show()
    
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()
